package com.tdunning;
import com.tdunning.math.stats.AVLTreeDigest;
import com.tdunning.math.stats.MergingDigest;
import com.tdunning.math.stats.TDigest;
import org.apache.mahout.math.jet.random.*;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.profile.GCProfiler;
import org.openjdk.jmh.profile.StackProfiler;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(1)
@State(Scope.Thread)
public class TDigestBench {
public enum TDigestFactory {
MERGE {
@Override
TDigest create(double compression) {
return new MergingDigest(compression, (int) (10 * compression));
}
},
AVL_TREE {
@Override
TDigest create(double compression) {
return new AVLTreeDigest(compression);
}
};
abstract TDigest create(double compression);
}
public enum DistributionFactory {
UNIFORM {
@Override
AbstractDistribution create(Random random) {
return new Uniform(0, 1, random);
}
},
SEQUENTIAL {
@Override
AbstractDistribution create(Random random) {
return new AbstractContinousDistribution() {
double base = 0;
@Override
public double nextDouble() {
base += Math.PI * 1e-5;
return base;
}
};
}
},
REPEATED {
@Override
AbstractDistribution create(final Random random) {
return new AbstractContinousDistribution() {
@Override
public double nextDouble() {
return random.nextInt(10);
}
};
}
},
GAMMA {
@Override
AbstractDistribution create(Random random) {
return new Gamma(0.1, 0.1, random);
}
},
NORMAL {
@Override
AbstractDistribution create(Random random) {
return new Normal(0.1, 0.1, random);
}
};
abstract AbstractDistribution create(Random random);
}
@Param({"100", "300"})
double compression;
@Param({"MERGE", "AVL_TREE"})
TDigestFactory tdigestFactory;
@Param({"NORMAL", "GAMMA"})
DistributionFactory distributionFactory;
Random random;
TDigest tdigest;
AbstractDistribution distribution;
double[] data = new double[1000000];
@Setup
public void setUp() {
random = ThreadLocalRandom.current();
tdigest = tdigestFactory.create(compression);
distribution = distributionFactory.create(random);
// first values are cheap to add, so pre-fill the t-digest to have more realistic results
for (int i = 0; i < 10000; ++i) {
tdigest.add(distribution.nextDouble());
}
for (int i = 0; i < data.length; ++i) {
data[i] = distribution.nextDouble();
}
}
@State(Scope.Thread)
public static class ThreadState {
int index = 0;
}
@Benchmark
public void timeAdd(MergeBench.ThreadState state) {
if (state.index >= data.length) {
state.index = 0;
}
tdigest.add(data[state.index++]);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(".*" + TDigestBench.class.getSimpleName() + ".*")
.resultFormat(ResultFormatType.CSV)
.result("overall-results.csv")
.addProfiler(GCProfiler.class)
.build();
new Runner(opt).run();
}
}